import { Point2 } from "../point/Point2";
import { Vector2 } from "../vector/Vector2";
import { Curve2 } from "./Curve2";
import { Curve2Type } from "./Curve2Type";
import { Precision } from "../Precision";
import { IntersectionType } from "./IntersectionType";


/**
* @description 2D线段
* @author xuld
* @date 2024-11-14
* @lastEditTime 2024-11-14 09:38:48
* @lastEditors xuld
*/
export class Segment2 extends Curve2 {
    private _start: Point2;
    private _end: Point2;
    readonly type = Curve2Type.Segment;

    constructor(start: Point2, end: Point2) {
        super(Curve2Type.Segment);
        this._start = start.clone();
        this._end = end.clone();
    }

    /**
    * @description 克隆曲线
    * @return 克隆的曲线
    */
    public clone(): Segment2 {
        return new Segment2(this._start, this._end);
    }

    /**
     * @description 起点
     * @return 起点
     */
    public get start(): Point2 {
        return this._start;
    }

    /**
     * @description 终点
     * @return 终点
     */
    public get end(): Point2 {
        return this._end;
    }

    /**
     * @description 长度
     * @return 长度
     */
    public get length(): number {
        return this._start.distance(this._end);
    }

    /**
     * @description 转换为字符串表示
     * @returns 格式化的点字符串
     */
    public toString(): string {
        return `{"type": "Segment2", "start": ${this._start.toString()}, "end": ${this._end.toString()}}`;
    }

    /**
    * @description 求曲线在参数 u 处的坐标
    * @param u - 参数方程的参数
    * @return 坐标
    */
    public d0(u: number): Point2 {
        // 线性插值
        return new Point2(
            this._start.x + (this._end.x - this._start.x) * u,
            this._start.y + (this._end.y - this._start.y) * u
        );
    }

    /**
     * @description 求曲线在参数 u 处的坐标和切线
     * @param u - 参数方程的参数
     * @return 坐标和切线
     */
    public d1(u: number): { point: Point2, tangent: Vector2 } {
        const point = this.d0(u);
        // 切线就是终点指向起点的向量的单位向量
        const tangent = this._end.vectorTo(this._start).normalize();
        return { point, tangent };
    }

    /**
    * @description 点是否在线内
    * @param p - 点
    * @param epsilon - 点在线上的误差, 默认 0.00001
    * @return true 点在线上，false 点不在线上
    */
    public containPoint(p: Point2, epsilon: number = Precision.EPSILON5): boolean {
        // 改进点在线段上的判断算法
        const v1 = this._end.vectorTo(this._start);
        const v2 = p.vectorTo(this._start);

        // 检查点是否共线
        const cross = v1.coord.cross(v2.coord);
        if (Math.abs(cross) > epsilon) {
            return false;
        }

        // 检查点是否在线段范围内
        const dot = v1.coord.dot(v2.coord);

        return dot >= -epsilon && dot <= v1.coord.dot(v1.coord) + epsilon;
    }

    /**
    * @description 求点 point 到曲线的最近投影点
    * @param point - 点
    * @return 最近的投影点及其u值
    */
    public projectPoint(point: Point2): { point: Point2, u: number } | undefined {
        const v = this._end.vectorTo(this._start);
        const w = point.vectorTo(this._start);

        const c1 = w.coord.dot(v.coord);
        const c2 = v.coord.dot(v.coord);

        if (c2 === 0) {
            return { point: this._start.clone(), u: 0 };
        }

        const u = c1 / c2;
        if (u < 0) {
            return { point: this._start.clone(), u: 0 };
        }
        if (u > 1) {
            return { point: this._end.clone(), u: 1 };
        }

        return { point: this.d0(u), u };
    }

    /**
     * @description 求两条曲线的交点
     * @param curve - 一条曲线
     * @param between - 是否在两条曲线之间，默认 false
     * @return 交点数组
     */
    public getCrossPoints(curve: Curve2, between: boolean = false): Array<Point2> {
        if (curve.curveType === Curve2Type.Segment) {
            return this.getSegmentCrossPoints(curve as Segment2, between);
        } else if (curve.curveType === Curve2Type.Arc) {
            return curve.getCrossPoints(this, between);
        }
        return [];
    }

    /**
     * @description 求两条线段的交点
     * @param other - 另一条线段
     * @param between - 是否在两条线段之间，默认 false
     * @return 交点数组
     */
    public getSegmentCrossPoints(other: Segment2, between: boolean): Array<Point2> {
        const v1 = this._end.vectorTo(this._start);
        const v2 = other.end.vectorTo(other.start);
        const v3 = other.start.vectorTo(this._start);

        const cross1 = v1.coord.cross(v2.coord);
        if (Math.abs(cross1) < Precision.EPSILON5) {
            // 处理共线情况
            if (Math.abs(v3.coord.cross(v1.coord)) < Precision.EPSILON5) {

                const points: Point2[] = [];
                // 检查端点是否在另一条线段上
                if (this.containPoint(other.start)) points.push(other.start.clone());
                if (this.containPoint(other.end)) points.push(other.end.clone());
                if (other.containPoint(this._start)) points.push(this._start.clone());
                if (other.containPoint(this._end)) points.push(this._end.clone());
                return points;
            }
            return []; // 平行不相交
        }

        const t = v3.coord.cross(v2.coord) / cross1;
        const s = v3.coord.cross(v1.coord) / cross1;

        if (between && (t < 0 || t > 1 || s < 0 || s > 1)) {
            return [];
        }

        return [this.d0(t)];
    }

    /**
     * @description 曲线细分
     * @return 细分点数组
     */
    public tessellate(): Array<Point2> {
        return [this._start, this._end];
    }

    /**
    * @description 获取点在线段上的参数
    * @param point - 点
    * @return 点在线段上的参数 (0-1 之间的值)
    * @throws 如果点不在线段上则抛出错误
    */
    public getParameter(point: Point2): number {
        if (!this.containPoint(point)) {
            return Precision.ERROR;
        }

        const v: Vector2 = this._end.vectorTo(this._start);
        const w: Vector2 = point.vectorTo(this._start);

        // 如果线段长度为0
        if (v.coord.dot(v.coord) === 0) {
            return 0;
        }

        // 计算参数 u = (w·v) / (v·v)
        const u = w.coord.dot(v.coord) / v.coord.dot(v.coord);

        // 处理数值误差,确保返回值在 [0,1] 范围内
        if (Math.abs(u) < Precision.EPSILON5) {
            return Precision.ERROR;
        }
        if (Math.abs(u - 1) < Precision.EPSILON5) {
            return Precision.ERROR;
        }
        return u;
    }

    /**
    * @description 线段相交性接口
    * @param curve2 - 另一条曲线
    * @return 相交类型和交点
    */
    public intersect(curve2: Curve2): { type: IntersectionType, intersectPoints: Array<Point2> } {
        // 获取交点
        const intersectPoints = this.getCrossPoints(curve2, true);

        // 如果是同类型的曲线（都是线段）
        if (curve2.curveType === Curve2Type.Segment) {
            const segment2 = curve2 as Segment2;

            // 检查是否完全重合
            if ((this._start.equals(segment2.start) && this._end.equals(segment2.end)) ||
                (this._start.equals(segment2.end) && this._end.equals(segment2.start))) {
                return { type: IntersectionType.equal, intersectPoints: [] };
            }

            // 检查端点是否相连
            if (this._start.equals(segment2.end) ||
                this._start.equals(segment2.start) ||
                this._end.equals(segment2.start) ||
                this._end.equals(segment2.end)) {
                // 找到相连的点作为交点
                const connectPoint = this._start.equals(segment2.end) ? this._start.clone() :
                    this._start.equals(segment2.start) ? this._start.clone() :
                        this._end.equals(segment2.start) ? this._end.clone() :
                            this._end.clone();
                return { type: IntersectionType.connect, intersectPoints: [connectPoint] };
            }

            // 检查是否共线
            const v1 = this._end.vectorTo(this._start);
            const v2 = segment2.end.vectorTo(segment2.start);
            const v3 = segment2.start.vectorTo(this._start);

            if (Math.abs(v3.coord.cross(v1.coord)) < Precision.EPSILON5 &&
                Math.abs(v1.coord.cross(v2.coord)) < Precision.EPSILON5) {

                // 检查包含关系
                const thisContainsOther = this.containPoint(segment2.start) && this.containPoint(segment2.end);
                const otherContainsThis = segment2.containPoint(this._start) && segment2.containPoint(this._end);

                if (thisContainsOther) {
                    return { type: IntersectionType.superSet, intersectPoints: [] };
                }
                if (otherContainsThis) {
                    return { type: IntersectionType.subSet, intersectPoints: [] };
                }

                // 部分重叠
                if (this.containPoint(segment2.start) || this.containPoint(segment2.end) ||
                    segment2.containPoint(this._start) || segment2.containPoint(this._end)) {
                    return { type: IntersectionType.overlap, intersectPoints: [] };
                }
            }
        }

        // 如果没有交点
        if (intersectPoints.length === 0) {
            return { type: IntersectionType.disjoint, intersectPoints: [] };
        }

        // 相交（有交点）
        return { type: IntersectionType.intersect, intersectPoints };
    }

    /**
     * 获取曲线在指定参数位置的切向量
     * @param t 参数值
     * @returns 切向量
     */
    public tangentAt(t: number): Vector2 {
        // 线段的切向量是固定的，就是从起点指向终点的单位向量
        const direction = this._start.vectorTo(this._end);
        return direction.normalize();
    }

    /**
     * 获取曲线在指定参数位置的曲率
     * @param t 参数值
     * @returns 曲率值
     */
    public curvatureAt(t: number): number {
        // 直线的曲率恒为0
        return 0;
    }
}